package level.editor.control.layer;

import utility.ImageUtility;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;

import javax.imageio.ImageIO;
import javax.swing.Icon;

import level.editor.control.collision.CollisionHandler.CollisionDot;
import level.editor.control.collision.CollisionLayer;
import level.editor.control.object.ObjectLayer;
import level.editor.control.tile.TileConstants;
import level.editor.control.tile.TileLayer;
import level.model.Addable;
import level.model.Collision;
import level.model.GameObject;
import level.model.StateCollisionLayer;
import level.model.StateLayer;
import level.model.StateObjectLayer;
import level.model.StateTileLayer;
import level.model.Tile;
import level.model.utility.StateHandler;

public class LayerPainter
{
	public static final int SIZE = 6;
	public static final int DOT_OFFSET = 3;
	public static final Stroke STROKE = new BasicStroke(3f);

	public static void paintGhost(Graphics2D g, Addable ghost, Icon icon,
			Point origin, Layer layer)

	{
		if (ghost instanceof Tile)
		{
			Tile tile = (Tile) ghost;
			icon.paintIcon(layer, g, (int) origin.getX() + tile.getX(),
					(int) origin.getY() + tile.getY());
		}

		if (ghost instanceof CollisionDot)
		{
			CollisionDot dot = (CollisionDot) ghost;
			icon.paintIcon(layer, g, (int) (origin.getX() + dot.getPoint()
					.getX()) - DOT_OFFSET, (int) (origin.getY() + dot
					.getPoint().getY()) - DOT_OFFSET);
		}
		
		if (ghost instanceof GameObject)
		{
			GameObject o = (GameObject) ghost;
			icon.paintIcon(layer, g, (int) origin.getX() + o.getX(),
					(int) origin.getY() + o.getY());
		}
	}

	public static void updateImage(Layer layer, StateLayer stateLayer,
			Addable addable)
	{
		if (layer instanceof TileLayer)
			paintTiles((TileLayer) layer, (StateTileLayer) stateLayer,
					(Tile) addable);
		else if (layer instanceof CollisionLayer)
		{
			updateImage((CollisionLayer) layer,
					(StateCollisionLayer) stateLayer);
		}
		else if (layer instanceof ObjectLayer)
			paintObjects((ObjectLayer) layer, (StateObjectLayer) stateLayer,
					(GameObject) addable);

		// else if (layer instanceof ObjectLayer)
		// {
		// updateImage((ObjectLayer) layer,
		// (StateObjectLayer) stateLayer);
		// }
	}

	public static void updateImage(Layer layer, StateLayer stateLayer)
	{
		if (!stateLayer.isEmpty())
		{
			if (stateLayer instanceof StateCollisionLayer)
				((StateCollisionLayer) stateLayer).calculateParameters();
			Dimension size = StateHandler.getSize(stateLayer);
			Point topLeft = StateHandler.getTopLeft(stateLayer);

			LayerImage image = new LayerImage((int) size.getWidth(),
					(int) size.getHeight());
			layer.setImage(image);
			image.setLeft((int) topLeft.getX());
			image.setTop((int) topLeft.getY());

			Graphics2D g = image.createGraphics();
			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);

			if (stateLayer instanceof StateTileLayer)
			{
				paintTilesFromState((StateTileLayer) stateLayer, g, topLeft);
			}
			else if (stateLayer instanceof StateCollisionLayer)
			{
				paintCollisionsFromState((CollisionLayer) layer,
						(StateCollisionLayer) stateLayer, g, topLeft);
			}
			else if (stateLayer instanceof StateObjectLayer)
			{
				paintObjectsFromState((StateObjectLayer) stateLayer, g, topLeft);
			}
		}
		else
		{
			layer.setImage(null);
		}
	}

	public static void updateImage(Layer layer, LayerImage image)
	{
		if (layer instanceof TileLayer)
			updateImage((TileLayer) layer, image);
		else if (layer instanceof CollisionLayer)
			System.out
					.println("Something wrong happened in LayerPainter.updateImage()");
	}

	private static void paintLayer(StateLayer layer, Graphics2D g, Point topLeft)
	{
		if (layer instanceof StateTileLayer)
		{
			paintTilesFromState((StateTileLayer) layer, g, topLeft);
		}
		else if (layer instanceof StateCollisionLayer)
		{
			System.out.println("Uh oh, problem in LayerPainter.paintLayer()");
		}
		else if (layer instanceof StateObjectLayer)
		{
			paintObjectsFromState((StateObjectLayer) layer, g, topLeft);
		}
	}

	private static void paintTiles(TileLayer layer, StateTileLayer stateLayer,
			Tile tile)
	{
		if (layer.getImage() == null)
		{
			// Write methods that calculate size and topleft corner of a
			// statelayer. This is for the initial load
			LayerImage image = new LayerImage(TileConstants.TILE_SIZE,
					TileConstants.TILE_SIZE);
			layer.setImage(image);
			image.setTop(tile.getY());
			image.setLeft(tile.getX());
			Graphics2D g = image.createGraphics();
			paintLayer(stateLayer, g,
					new Point(image.getLeft(), image.getTop()));
			g.dispose();
		}

		else
		{
			LayerImage image = layer.getImage();
			if (!isInside(image, tile))
			{
				image = resizeImage(image, tile);
				layer.setImage(image);
			}
			Graphics2D g = image.createGraphics();
			try
			{
				for (int i = tile.getX() - image.getLeft(); i < tile.getX()
						- image.getLeft() + tile.getW(); i++)
					for (int j = tile.getY() - image.getTop(); j < tile.getY()
							- image.getTop() + tile.getH(); j++)
					{
						image.setRGB(i, j, TRANSPARENT);
					}

				g.drawImage(ImageUtility.makeColorTransparent(ImageIO
						.read(new File(tile.getIconPath())), new Color(255, 0,
						255)), tile.getX() - image.getLeft(), tile.getY()
						- image.getTop(),
						tile.getX() + tile.getW() - image.getLeft(),
						tile.getY() + tile.getH() - image.getTop(), tile
								.getSx(), tile.getSy(),
						tile.getSx() + tile.getSw(),
						tile.getSy() + tile.getSh(), null);
				g.dispose();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}

	private static void paintObjects(ObjectLayer layer,
			StateObjectLayer stateLayer, GameObject obj)
	{
		if (layer.getImage() == null)
		{
			// Write methods that calculate size and topleft corner of a
			// statelayer. This is for the initial load
			LayerImage image = new LayerImage(obj.getW(), obj.getH());
			layer.setImage(image);
			image.setTop(obj.getY() - obj.getW() / 2);
			image.setLeft(obj.getX() - obj.getH() / 2);
			Graphics2D g = image.createGraphics();
			paintLayer(stateLayer, g,
					new Point(image.getLeft(), image.getTop()));
			g.dispose();
		}

		else
		{
			LayerImage image = layer.getImage();
			Rectangle rect = new Rectangle(obj.getX() - obj.getH() / 2,
					obj.getY() - obj.getW() / 2, obj.getW(), obj.getH());
			if (!isInside(image, rect))
			{
				image = resizeImage(image, rect);
				layer.setImage(image);
			}
			Graphics2D g = image.createGraphics();
			try
			{
				g.setColor(Color.WHITE);
				g.setStroke(OBJECT_STROKE);
				g.drawImage(ImageUtility.makeColorTransparent(ImageIO
						.read(new File(obj.getIconPath())), new Color(255, 0,
						255)), obj.getX() - image.getLeft() - obj.getW() / 2,
						obj.getY() - image.getTop() - obj.getH() / 2,
						obj.getX() + obj.getW() / 2 - image.getLeft(),
						obj.getY() + obj.getH() / 2 - image.getTop(), 0, 0, obj
								.getW(), obj.getH(), null);
				g.draw(new Rectangle(obj.getX() - image.getLeft() - obj.getW()
						/ 2, obj.getY() - image.getTop() - obj.getH() / 2, obj.getW() - 1, obj.getH() - 1));
				g.dispose();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}

	private static void paintTilesFromState(StateTileLayer layer, Graphics2D g,
			Point topLeft)
	{
		HashMap<String, Image> images = new HashMap<String, Image>();
		for (Tile tile : layer.getTiles().values())
		{
			Image image = null;
			if (images.containsKey(tile.getIconPath()))
				image = images.get(tile.getIconPath());
			else
			{
				try
				{
					image = ImageUtility.makeColorTransparent(
							ImageIO.read(new File(tile.getIconPath())),
							new Color(255, 0, 255));
					images.put(tile.getIconPath(), image);
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}

			}
			g.drawImage(image, tile.getX() - (int) topLeft.getX(), tile.getY()
					- (int) topLeft.getY(), tile.getX() + tile.getW()
					- (int) topLeft.getX(), tile.getY() + tile.getH()
					- (int) topLeft.getY(), tile.getSx(), tile.getSy(),
					tile.getSx() + tile.getSw(), tile.getSy() + tile.getSh(),
					null);
		}
	}

	private static void paintCollisionsFromState(CollisionLayer layer,
			StateCollisionLayer stateLayer, Graphics2D g, Point topLeft)
	{
		g.setStroke(STROKE);
		g.setColor(Color.RED);

		for (Collision collision : stateLayer.getCollisions())
		{
			Line2D line = collision.getLine();

			Point p1 = new Point();
			Point p2 = new Point();
			p1.x = (int) (line.getX1() - topLeft.getX());
			p1.y = (int) (line.getY1() - topLeft.getY());
			p2.x = (int) (line.getX2() - topLeft.getX());
			p2.y = (int) (line.getY2() - topLeft.getY());

			Ellipse2D ellipse = new Ellipse2D.Float(p1.x - DOT_OFFSET, p1.y
					- DOT_OFFSET, SIZE, SIZE);
			g.fill(ellipse);

			ellipse = new Ellipse2D.Float(p2.x - DOT_OFFSET, p2.y - DOT_OFFSET,
					SIZE, SIZE);
			g.fill(ellipse);

			Line2D newLine = new Line2D.Float(p1, p2);

			g.draw(newLine);
		}
	}

	private static void paintObjectsFromState(StateObjectLayer layer,
			Graphics2D g, Point topLeft)
	{
		HashMap<String, Image> images = new HashMap<String, Image>();
		for (GameObject obj : layer.getObjects())
		{
			g.setColor(Color.WHITE);
			g.setStroke(OBJECT_STROKE);
			Image image = null;
			if (images.containsKey(obj.getIconPath()))
				image = images.get(obj.getIconPath());
			else
			{
				try
				{
					image = ImageUtility.makeColorTransparent(
							ImageIO.read(new File(obj.getIconPath())),
							new Color(255, 0, 255));
					images.put(obj.getIconPath(), image);
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}

			}
			g.drawImage(image, obj.getX() - (int) topLeft.getX() - obj.getW()
					/ 2, obj.getY() - (int) topLeft.getY() - obj.getH() / 2,
					obj.getX() + obj.getW() / 2 - (int) topLeft.getX(),
					obj.getY() + obj.getH() / 2 - (int) topLeft.getY(), 0, 0,
					obj.getW(), obj.getH(), null);
			g.draw(new Rectangle(obj.getX() - (int) topLeft.getX() - obj.getW()
					/ 2, obj.getY() - (int) topLeft.getY() - obj.getH() / 2, obj.getW() - 1, obj.getH() - 1));
		}
	}

	private static void updateImage(TileLayer layer, LayerImage image)
	{
		if (layer.getImage() == null)
		{
			layer.setImage(image);
		}
		else
		{
			LayerImage oldImage = layer.getImage();
			Rectangle rect = new Rectangle(image.getLeft(), image.getTop(),
					image.getWidth(), image.getHeight());
			if (!isInside(oldImage, rect))
			{
				oldImage = resizeImage(oldImage, rect);
				layer.setImage(oldImage);
			}
			Graphics2D g = oldImage.createGraphics();
			for (int i = 0; i < image.getWidth(); i++)
				for (int j = 0; j < image.getHeight(); j++)
				{
					oldImage.setRGB(i + image.getLeft() - oldImage.getLeft(), j
							+ image.getTop() - oldImage.getTop(), TRANSPARENT);
				}

			g.drawImage(image, image.getLeft() - oldImage.getLeft(),
					image.getTop() - oldImage.getTop(), null);
			g.dispose();
		}
	}

	private static LayerImage resizeImage(LayerImage image, Tile tile)
	{
		boolean left = false;
		boolean top = false;
		boolean right = false;
		boolean bottom = false;

		if (image.getLeft() > tile.getX())
			left = true;
		if (image.getTop() > tile.getY())
			top = true;
		if (image.getLeft() + image.getWidth() <= tile.getX())
			right = true;
		if (image.getTop() + image.getHeight() <= tile.getY())
			bottom = true;

		int width = left ? image.getLeft() - tile.getX() + image.getWidth()
				: right ? tile.getX() - image.getLeft() + tile.getW() : image
						.getWidth();
		int height = top ? image.getTop() - tile.getY() + image.getHeight()
				: bottom ? tile.getY() - image.getTop() + tile.getH() : image
						.getHeight();

		LayerImage newImage = new LayerImage(width, height);
		Graphics2D g = newImage.createGraphics();

		g.drawImage(image, left ? image.getLeft() - tile.getX() : 0,
				top ? image.getTop() - tile.getY() : 0, null);
		g.dispose();
		newImage.setLeft(Math.min(tile.getX(), image.getLeft()));
		newImage.setTop(Math.min(tile.getY(), image.getTop()));
		return newImage;
	}

	private static LayerImage resizeImage(LayerImage oldImage, Rectangle rect)
	{
		Rectangle oldRectangle = new Rectangle(new Point(oldImage.getLeft(),
				oldImage.getTop()), new Dimension(oldImage.getWidth(),
				oldImage.getHeight()));
		Rectangle2D newRectangle = oldRectangle.createUnion(rect);

		LayerImage newImage = new LayerImage((int) newRectangle.getWidth(),
				(int) newRectangle.getHeight());
		Graphics2D g = newImage.createGraphics();
		newImage.setLeft((int) newRectangle.getX());
		newImage.setTop((int) newRectangle.getY());

		g.drawImage(oldImage, oldImage.getLeft() - newImage.getLeft(),
				oldImage.getTop() - newImage.getTop(), null);
		g.dispose();
		return newImage;
	}

	private static boolean isInside(LayerImage image, Tile tile)
	{
		Rectangle rect = new Rectangle(image.getLeft(), image.getTop(),
				image.getWidth(), image.getHeight());
		return rect.contains(tile.getX(), tile.getY());
	}

	private static boolean isInside(LayerImage oldImage, Rectangle rect)
	{
		Rectangle oldRect = new Rectangle(oldImage.getLeft(),
				oldImage.getTop(), oldImage.getWidth(), oldImage.getHeight());
		return oldRect.contains(rect);
	}

	private static final int TRANSPARENT = 0x000000;
	private static final Stroke OBJECT_STROKE = new BasicStroke(1f);
}
